package com.hp.bon.sgw.wkflow;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * BON业务流程定义文件解析器
 * 
 * @author wuzhih
 * 
 */
@Path("/")
public class WkflowDefParser {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = LoggerFactory.getLogger(WkflowDefParser.class);

	private volatile Map<String, FlowDef> allFlowDefs = Collections.emptyMap();
	/**
	 * BON业务流程定义文件的目录
	 */
	private String flowDefXml;
	private String flowFileExt = ".xml";

	public void init() {
		try {
			loadFlowDefXml();
		} catch (Exception e) {
			logger.warn("caught err:", e);
		}
	}

	public Map<String, FlowDef> getAllFlowDefs() {
		return allFlowDefs;
	}

	@GET
	@Path("config/reloadworkflowfile")
	@Produces(MediaType.TEXT_PLAIN)
	public String loadFlowDefXml() {
		File _file = new File(flowDefXml);
		logger.info("load bon workflow defines from dir:" + _file.getAbsolutePath());
		File[] files = _file.listFiles();
		Map<String, FlowDef> newAllFlowDefs = new LinkedHashMap<String, FlowDef>();
		for (File file : files) {
			if (file.isFile() && file.getName().endsWith(this.flowFileExt)) {
				try {
					SAXReader reader = new SAXReader();
					Document document = null;
					document = reader.read(file);
					parseFlowDefXml(document, newAllFlowDefs);
				} catch (DocumentException e) {
					logger.error("load err: " + file + " " + e);
				}
			}

		}
		this.allFlowDefs = newAllFlowDefs;
		logger.info("load total workflow def " + newAllFlowDefs.size());
		return "SUCCESS";

	}

	@SuppressWarnings("unchecked")
	private void parseFlowDefXml(Document document, Map<String, FlowDef> newAllFlowDefs) {
		Element root = document.getRootElement();
		Element flowEl = root.element("flow");
		String capability = flowEl.attributeValue("capability");
		logger.info("parse flow of capability " + capability);
		FlowDef flowDef = new FlowDef();
		flowDef.beforeFlowScript = flowEl.elementText("before-flow-script");
		flowDef.capability = capability;
		flowDef.timeOutSeconds = Integer.parseInt(flowEl.attributeValue("timeout", "120"));
		Element initEle = flowEl.element("init");
		if (initEle != null) {
			flowDef.initTransitionDefs = parseStepTransitionDefs(flowEl.element("init").element("transitions"));
		}
		flowDef.globalVarsDefMap = parseFlowVars(flowEl.element("def-var"));
		List<Element> stepEls = (List<Element>) flowEl.elements("step");
		for (Element stepEL : stepEls) {
			String stepName = stepEL.attributeValue("name");
			StepDef stepDef = new StepDef();
			if (flowDef.stepDefs.containsKey(stepName)) {
				logger.warn("duplicate step defined " + stepName);
				continue;
			}
				flowDef.stepDefs.put(stepName, stepDef);
				stepDef.name = stepName;
				stepDef.isAsyn = "true".equalsIgnoreCase(stepEL.attributeValue("asyn"));
				stepDef.beforeSendScript = stepEL.elementText("before-request-script");
				stepDef.beforeRespScript = stepEL.elementText("before-response-script");
				List<Element> requestMsgELs = (List<Element>) stepEL.element("request").elements("Message");
				stepDef.sendRequests = parseRequestMessageEls(requestMsgELs);
				stepDef.stepVarsDefMap = parseFlowVars(stepEL.element("response").element("def-var"));
				stepDef.transitionDefs = parseStepTransitionDefs(stepEL.element("response").element("transitions"));
				stepDef.returnDef = parseStepReturnDef(stepEL.element("response").element("return"));
		}
		if (newAllFlowDefs.containsKey(flowDef.capability)) {
			logger.warn("duplicate flow define found for same capability " + flowDef.capability);
		} else {
			if (validateFlowDef(flowDef)) {
				newAllFlowDefs.put(flowDef.capability, flowDef);
			}
		}

	}

	private boolean validateFlowDef(FlowDef flowDef) {
		boolean invalid = false;
		StringBuilder errMsg = new StringBuilder();
		if (noValue(flowDef.capability)) {
			invalid = true;
			errMsg.append("\r\n flow capability is null!");
		}
		if (flowDef.stepDefs.isEmpty()) {
			invalid = true;
			errMsg.append("\r\n flow no any steps !");
		}

		for (StepDef stepDef : flowDef.stepDefs.values()) {
			if (noValue(stepDef.name)) {
				invalid = true;
				errMsg.append("\r\n step has no name !");
			}
			if (stepDef.sendRequests.isEmpty()) {
				invalid = true;
				errMsg.append("\r\n step has no any request defined !" + stepDef.name);
			}
			// check step ref
			for (TransitionDef transDef : stepDef.transitionDefs) {
				if (!flowDef.stepDefs.containsKey(transDef.nextStep)) {
					invalid = true;
					errMsg.append("\r\n can't find step  " + transDef.nextStep + " referenced in transitions  " + " of step " + stepDef.name);
				}
			}
			if (stepDef.transitionDefs.isEmpty() && stepDef.returnDef == null) {
				invalid = true;
				errMsg.append("\r\n step must has transitions or return define  " + stepDef.name);
			}
		}
		if (invalid) {
			logger.warn(errMsg.toString());
		}
		return !invalid;
	}

	private boolean noValue(String value) {
		return value == null || value.trim().equals("");
	}

	@SuppressWarnings("unchecked")
	private ReturnDef parseStepReturnDef(Element returnEL) {
		if (returnEL == null) {
			return null;
		}
		String rcCode = returnEL.attributeValue("rc");
		rcCode = (rcCode == null) ? null : (rcCode.trim().length() > 0 ? rcCode.trim() : null);
		ReturnDef returnDef = new ReturnDef();
		returnDef.rcCode = rcCode;
		List<Element> returnELs = returnEL.elements();
		returnDef.returnDATA = (returnELs.isEmpty()) ? returnEL.getTextTrim() : returnELs.get(0).asXML();
		return returnDef;
	}

	@SuppressWarnings("unchecked")
	private List<TransitionDef> parseStepTransitionDefs(Element transEL) {
		if (transEL == null) {
			return Collections.emptyList();
		}
		List<Element> allSwitchs = transEL.elements("when");
		List<TransitionDef> trnsitions = new ArrayList<TransitionDef>(allSwitchs.size());
		for (Element whenEL : allSwitchs) {
			TransitionDef transDef = new TransitionDef();
			transDef.expression = whenEL.attributeValue("cond");
			transDef.nextStep = whenEL.attributeValue("to");
			trnsitions.add(transDef);
		}
		return trnsitions;
	}

	private List<SendRequestDef> parseRequestMessageEls(List<Element> requestMsgELs) {
		List<SendRequestDef> sendRequests = new LinkedList<SendRequestDef>();
		for (Element reqMsg : requestMsgELs) {
			String request = reqMsg.attributeValue("request");
			String response = reqMsg.attributeValue("response");
			SendRequestDef reqDef = new SendRequestDef();
			sendRequests.add(reqDef);
			reqDef.reqVarName = request;
			reqDef.reqMsgDef = reqMsg;
			reqDef.respVarName = response;
		}
		return sendRequests;
	}

	@SuppressWarnings("unchecked")
	private Map<String, String> parseFlowVars(Element element) {
		if (element == null) {
			return Collections.emptyMap();
		}
		List<Element> varEls = element.elements("var");
		Map<String, String> vars = new LinkedHashMap<String, String>();
		for (Element varEL : varEls) {
			vars.put(varEL.attributeValue("name"), varEL.getTextTrim());
		}
		return vars;
	}

	public void setFlowDefXml(String flowDefXml) {
		this.flowDefXml = flowDefXml;
	}

}